package com.lanlengran.hotupdatetest.hook;

import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;

import com.lanlengran.hotupdatetest.PluginActivity;
import com.lanlengran.hotupdatetest.ProxyActivity;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import dalvik.system.DexClassLoader;

public class DexUtils {
    private static final String TAG = "DexUtils";
    private static final String KEY_ACTIVITY_NAME = "KEY_ACTIVITY_NAME";
    public static final int LAUNCH_ACTIVITY = 100;

    public static void init(Context context) {
        loadClass(context);
        hookStartActivityFirst(context);
        hookStartActivityLast(context);
    }


    /**
     * 去除反射相关代码后的，等价代码为
     * <p>
     * ClassLoader pathClassLoader=context.getClassLoader()；
     * DexPathList hostPathList=pathClassLoader.pathList;
     * Element[] hostDexElements=hostPathList.dexElements;
     * <p>
     * String apkPath="/sdcard/output.dex";
     * ClassLoader dexClassLoader=new DexClassLoader(apkPath,context.getCacheDir().getAbsolutePath(),null,pathClassLoader);
     * <p>
     * DexPathList pluginPathList= dexClassLoader.pathList;
     * Element[] pluginDexElements=pluginPathList.dexElements;
     * <p>
     * Element[] newDexElements=new Element[hostDexElements.length+pluginDexElements.length];
     * <p>
     * System.arraycopy(pluginElements,0,newDexElements,0,pluginElements.length);
     * System.arraycopy(hostDexElements,0,newDexElements,pluginElements.length,hostDexElements.length);
     * <p>
     * hostPathList.dexElements=newDexElements;
     *
     * @param context
     */
    public static void loadClass(Context context) {
        try {
            Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = clazz.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);

            ClassLoader pathClassLoader = context.getClassLoader();
            Object hostPathList = pathListField.get(pathClassLoader);
            Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);

            String apkPath = "/sdcard/output.dex";
            ClassLoader dexClassLoader = new DexClassLoader(apkPath, context.getCacheDir().getAbsolutePath(), null, pathClassLoader);

            Object pluginPathList = pathListField.get(dexClassLoader);
            Object[] pluginElements = (Object[]) dexElementsField.get(pluginPathList);

            Object[] newDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(), hostDexElements.length + pluginElements.length);

            System.arraycopy(pluginElements, 0, newDexElements, 0, pluginElements.length);

            System.arraycopy(hostDexElements, 0, newDexElements, pluginElements.length, hostDexElements.length);


            dexElementsField.set(hostPathList, newDexElements);
            Log.d(TAG, "loadClass: 加载成功");
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "加载class出错，loadClass: ", e.getCause());
        }
    }

    public static void hookStartActivityFirst(Context context) {
        try {
            Class<?> activityManager = ActivityManager.class;
            Field IActivityManagerSingletonField = activityManager.getDeclaredField("IActivityManagerSingleton");
            IActivityManagerSingletonField.setAccessible(true);
            Object IActivityManagerSingleton = IActivityManagerSingletonField.get(null);

            Class<?> SingletonClazz = Class.forName("android.util.Singleton");
            Field mInstanceField = SingletonClazz.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            Object mInstance = mInstanceField.get(IActivityManagerSingleton);

            Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{iActivityManagerClass},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            Log.d(TAG, "hookActivity.invoke: " + method.getName());
                            if ("startActivity".equals(method.getName())) {
                                Intent intent = (Intent) args[2];
//                        if(intent.resolveActivity(context.getPackageManager()) == null) {
                                Log.d(TAG, "activity找不到: " + method.getName());
                                String oldName = intent.getComponent().getClassName();
                                Class<?> componentNameClazz = Class.forName("android.content.ComponentName");
                                Field mInstanceField = componentNameClazz.getDeclaredField("mClass");
                                mInstanceField.setAccessible(true);

                                mInstanceField.set(intent.getComponent(), ProxyActivity.class.getName());
                                intent.putExtra("KEY_ACTIVITY_NAME", oldName);
//                        }
                            }
                            return method.invoke(mInstance, args);
                        }
                    });

            Log.d(TAG, "hookActivity: proxyInstance===" + proxyInstance.toString());
            mInstanceField.set(IActivityManagerSingleton, proxyInstance);


        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static void hookStartActivityLast(Context context) {
        try {
            Class<?> ActivityThreadClazz = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
            Object activityThread = currentActivityThreadMethod.invoke(null);


            Field mHField = ActivityThreadClazz.getDeclaredField("mH");
            mHField.setAccessible(true);
            Handler mH = (Handler) mHField.get(activityThread);


            Field mCallbackField = Handler.class.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            mCallbackField.set(mH, new Handler.Callback() {

                @Override
                public boolean handleMessage(Message msg) {
                    try {
                        Log.d(TAG, "hookStartActivityLast.handleMessage: " + msg.obj);
                        switch (msg.what) {
                            case LAUNCH_ACTIVITY: {
                                Object activityClientRecord = msg.obj;

                                Class<?> ActivityClientRecordClazz = Class.forName("android.app.ActivityThread$ActivityClientRecord");
                                Field intentField = ActivityClientRecordClazz.getDeclaredField("intent");
                                intentField.setAccessible(true);
                                Intent intent= (Intent) intentField.get(activityClientRecord);

                                String oldName = intent.getStringExtra("KEY_ACTIVITY_NAME");
                                if (TextUtils.isEmpty(oldName)){
                                    return false;
                                }

                                Field mInstanceField = ComponentName.class.getDeclaredField("mClass");
                                mInstanceField.setAccessible(true);
                                mInstanceField.set(intent.getComponent(), oldName);

                            }
                            break;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return false;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
